
#ifndef	__ULOG_HPP_2010__
#define	__ULOG_HPP_2010__

#include <time.h>
#include <unistd.h>
#include <string.h>
#include <netdb.h>
#include <stdarg.h>

#include    <sys/types.h>
#include    <sys/socket.h>
#include    <netinet/in.h>
#include    <arpa/inet.h>



class	ULog
{
	#define	IP_BUF_SIZE	64

public	:
	explicit ULog( const char* hostname, int port )
		:	_hostname_p(NULL), _port(port), _udp_socket(0)
	{
		_hostname_p = strdup( hostname );

		int	ok = getip( _hostname_p, _ip, IP_BUF_SIZE );
		_alive = ok < 0 ? false : true;

		_udp_socket = socket( AF_INET, SOCK_DGRAM, 0 );
		_alive = _udp_socket < 0 ? false : true;

		_tcp_socket = socket( AF_INET, SOCK_STREAM, 0 );
		_alive = _tcp_socket < 0 ? false : true;
		_connected = false;

		__setupUdpSocket();
		__setupUlogsAddress();

		memset( _local_hostname, 0, sizeof(_local_hostname) );
		gethostname( _local_hostname, sizeof(_local_hostname) );

		_pid = getpid();

		memset( _log_buf, 0, sizeof(_log_buf) );
		memset( _buf, 0, sizeof(_buf) );

		_enable_debug = false;
		_enable_trace = false;
	}

	~ULog()
	{
		if ( _hostname_p ) {
			free( _hostname_p );
		}
	}

public	:
	void	log( const char* category, const char* level, const char* filename, int lineno, const char* msg )
	{
		if ( !_alive ) {
			return;
		}
		time_t	now = time(NULL);

		char*	p = _log_buf;
		int		n = 0;
		int		left = sizeof(_log_buf) - n - 1;
		int		s = 0;

		n = snprintf( p, left, "%ld\t%s\t%d\t-\t", now, _local_hostname, _pid );
		p += n;
		s += n;
		left -= n;
		n = snprintf( p, left, "%s\t%s\t%s\t%d\t%s\r\n", category, level, filename, lineno, msg );
		p += n;
		s += n;
		left -= n;

		//sendto( _udp_socket, _log_buf, s, 0, (const struct sockaddr *)&_ulogs_addr, _ulogs_addr_len );
		int ret = __tcp_send( _log_buf, s );
		if ( ret < 0 ) {
			fprintf(stderr, "get %d when send %s", ret, _log_buf);
		}
	}

	void	error( const char* category, const char* filename, int lineno, const char* format, ... )
	{
		if ( !_alive ) {
			return;
		}
		va_list	args;

		va_start( args, format );
		vsnprintf( _buf, sizeof(_buf) - 1, format, args );
		va_end( args );

		log( category, "ERROR", filename, lineno, _buf );
	}

	void	warn( const char* category, const char* filename, int lineno, const char* format, ... )
	{
		if ( !_alive ) {
			return;
		}
		va_list	args;

		va_start( args, format );
		vsnprintf( _buf, sizeof(_buf) - 1, format, args );
		va_end( args );

		log( category, "WARN", filename, lineno, _buf );
	}

	void	info( const char* category, const char* filename, int lineno, const char* format, ... )
	{
		if ( !_alive ) {
			return;
		}
		va_list	args;

		va_start( args, format );
		vsnprintf( _buf, sizeof(_buf) - 1, format, args );
		va_end( args );

		log( category, "INFO", filename, lineno, _buf );
	}

	void	debug( const char* category, const char* filename, int lineno, const char* format, ... )
	{
		if ( !_alive ) {
			return;
		}
		if ( !_enable_debug ) {
			return;
		}

		va_list	args;

		va_start( args, format );
		vsnprintf( _buf, sizeof(_buf) - 1, format, args );
		va_end( args );

		log( category, "DEBUG", filename, lineno, _buf );
	}

	void	trace( const char* category, const char* filename, int lineno, const char* format, ... )
	{
		if ( !_alive ) {
			return;
		}
		if ( !_enable_trace ) {
			return;
		}
		va_list	args;

		va_start( args, format );
		vsnprintf( _buf, sizeof(_buf) - 1, format, args );
		va_end( args );

		log( category, "TRACE", filename, lineno, _buf );
	}

	void	enableDebug( bool enabled )
	{
		_enable_debug = enabled;
	}

	void	enableTrace( bool enabled )
	{
		_enable_trace = enabled;
	}

private	:
	ULog( const ULog& );
	ULog& operator=( const ULog& );

private	:

	int		__tcp_send( const char* data, int  data_size )
	{
		if ( !_connected ) {
			__tryConnect();
		}
		if ( !_connected ) {
			return	-1;
		}

		ssize_t	sent = 0;
		for ( int i=0; i<5; ++i ) {
			ssize_t ret = send( _tcp_socket, data + sent, data_size - sent, 0 );
			if ( ret < 0 ) {
				shutdown( _tcp_socket, SHUT_RDWR );
				_connected = false;
				return	ret;
			}
			sent += ret;
			if (sent >= data_size) {
				break;
			}
		}

		return	0;
	}

	int		__tryConnect( void )
	{
		if ( _connected ) {
			return	0;
		}

		int	ret = connect( _tcp_socket, (struct sockaddr *)&_ulogs_addr, _ulogs_addr_len );
		if ( ret < 0 ) {
			return	ret;
		}
		_connected = true;

		return	0;
	}



	int		__setupUlogsAddress( void )
	{
		memset( &_ulogs_addr, 0, sizeof(sockaddr_in) );
		_ulogs_addr.sin_family = AF_INET;
		inet_pton( AF_INET, _ip, &(_ulogs_addr.sin_addr) );
		_ulogs_addr.sin_port = htons( static_cast< unsigned short >( _port ) );
		_ulogs_addr_len = sizeof(sockaddr_in);

		return	0;
	}

	int		__setupUdpSocket( void )
	{
		if ( !_alive ) {
			return	0;
		}

        const unsigned  SND_BUF_SIZE = 200 * 1024;
        const unsigned  RCV_BUF_SIZE = 200 * 1024;
        int     iOption, iReturn;

        iOption = SND_BUF_SIZE;
        iReturn = setsockopt( _udp_socket, SOL_SOCKET, SO_SNDBUF, &iOption, sizeof(iOption) );
        if ( iReturn < 0 ) {
            //ERROR ( CAT, 0, "Fail to setsockopt SO_SNDBUF (size:%d) : %s", iOption, strerror(errno) );
        }
        iOption = RCV_BUF_SIZE;
        iReturn = setsockopt( _udp_socket, SOL_SOCKET, SO_RCVBUF, &iOption, sizeof(iOption) );
        if ( iReturn < 0 ) {
            //ERROR ( CAT, 0, "Fail to setsockopt SO_RCVBUF (size:%d) : %s", iOption, strerror(errno) );
        }

        struct timeval  timeout;
        timeout.tv_sec = 0;
        timeout.tv_usec = 50000;
        iReturn = setsockopt( _udp_socket, SOL_SOCKET, SO_SNDTIMEO, &timeout, sizeof(timeout) );
        if ( iReturn < 0 ) {
            //ERROR ( CAT, 0, "Fail to setsockopt SO_SNDTIMEO : %s", strerror(errno) );
        }
        timeout.tv_sec = 0;
        timeout.tv_usec = 50000;
        iReturn = setsockopt( _udp_socket, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof(timeout) );
        if ( iReturn < 0 ) {
            //ERROR ( CAT, 0, "Fail to setsockopt SO_RCVTIMEO : %s", strerror(errno) );
        }

		return	0;
	}

private	:

	int	getip( const char* hostname, char* ip_buf, unsigned ip_buf_size )
	{
		char **pptr;
		struct hostent *hptr;

		if((hptr = gethostbyname(hostname)) == NULL) {
			return	-1;
		}

		switch(hptr->h_addrtype)
		{
			case AF_INET:
				pptr=hptr->h_addr_list;
				for(;*pptr!=NULL;pptr++) {
					memset( ip_buf, 0, ip_buf_size );
					inet_ntop(hptr->h_addrtype, *pptr, ip_buf, ip_buf_size);
					break;
				}
				break;
			case AF_INET6:
				break;
			default:
				return -2;
		}

		return	0;
	}


private	:
	char*	_hostname_p;
	int		_port;
	char	_ip[ IP_BUF_SIZE ];

	bool	_alive;

	int		_udp_socket;
	int		_tcp_socket;
	int		_connected;

    struct  sockaddr_in _ulogs_addr;
    socklen_t   _ulogs_addr_len;

	char	_local_hostname[ 32 ];
	pid_t	_pid;

	char	_log_buf[ 32768 ];
	char	_buf[ 32768 ];

	bool	_enable_debug;
	bool	_enable_trace;
};	//// class ULog


#endif	//// __ULOG_HPP_2010__


